package br.org.bertol.mestrado.engine;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.fitness.Objective;
import br.org.bertol.mestrado.engine.optimisation.OrdenateSpacialPosition;
import br.org.bertol.mestrado.engine.optimisation.entity.Particle;
import br.org.bertol.mestrado.engine.optimisation.moo.OrdenatePareto;
import br.org.bertol.mestrado.engine.optimisation.moo.ParetoMinMinDominace;
import br.org.bertol.mestrado.engine.optimisation.pso.LinearInertiaReduction;
import br.org.bertol.mestrado.engine.optimisation.pso.ParticleSwarmOptimization;
import br.org.bertol.mestrado.util.PsoConfiguration;

/**
 * @author contaqualquer
 */
public class EnginePso extends AbstractSearchEngine<String> {

    /***/
    private final transient List<ParticleSwarmOptimization<String>> resultPSO;

    /***/
    private final transient PsoConfiguration                        psoConfiguration;

    /**
     * @param startS
     * @param stopS
     * @param numEval
     * @param objectives
     * @param storeP
     * @param sid
     * @param typeMovement
     * @param initInertia
     * @param finalInertia
     * @param fatorInicialCognitivo
     * @param fatorInitSocial
     * @param depth
     * @param repositorySize
     * @param populationSize
     */
    public EnginePso(final CountDownLatch startS, final CountDownLatch stopS,
        final int numEval, final Objective[] objectives,
        final String storeP, final String sid,
        final PsoConfiguration configuration) {

        super(startS, stopS, numEval, storeP, sid, objectives);

        resultPSO = new ArrayList<ParticleSwarmOptimization<String>>();

        psoConfiguration = configuration;

        setName(getName() + " | Tamanho da população "
                + psoConfiguration.getPopulacao()
                + " | Fatores inerciais "
                + psoConfiguration.getInitialinertia() + " -> "
                + psoConfiguration.getFinalinertia() + " | Fatores cognitivos "
                + psoConfiguration.getFatorcognitivo() + " | Fatores social "
                + psoConfiguration.getFatorsocial()
                + " | Divisões do grid " + psoConfiguration.getDepth()
                + " | Tamanho do repositório "
                + psoConfiguration.getRepositorysize()
                + " | Tipo movimento " + psoConfiguration.getTypeMoviment());
    }

    @Override
    public final void run() {
        try {
            // deixa sistema ponto para entrar em ação
            startSystem.await();

            if (verify.isSystemEnable()) {
                // aramazena resultado do pso em arquivo
                PrintWriter outFile = new PrintWriter(new BufferedWriter(
                        new FileWriter(storePath + "/" + systemID
                        + "/resultado_avaliacao_" + systemID + "_PSO_"
                        + psoConfiguration.getTypeMoviment() + "_"
                        + typeEvaluation
                        + ".txt", true)));

                Logger.getLogger(this.getClass()).info("Start");

                outFile
                        .println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");

                outFile.println(getName());

                Logger.getLogger(this.getClass())
                        .debug(
                               "Fatores: inércia ["
                        + psoConfiguration.getInitialinertia()
                        + " : "
                        + psoConfiguration.getFinalinertia()
                        + "] | cognitivo => "
                        + psoConfiguration.getFatorcognitivo()
                        + " | social => "
                        + psoConfiguration.getFatorsocial()
                        + ". Aguarde.");

                final LinearInertiaReduction linearInertiaReduction = new LinearInertiaReduction(
                        psoConfiguration.getInitialinertia(), psoConfiguration
                        .getFinalinertia(), numIteracoes);

                final List<Particle<String>> swarm = randomSolution
                        .generateRandomPSOSolutions(
                                                    psoConfiguration
                        .getPopulacao(),
                                                    linearInertiaReduction,
                                                    psoConfiguration
                        .getFatorcognitivo(),
                                                    psoConfiguration
                        .getFatorsocial());

                // ordena a lista de part�culas
                Collections
                        .sort(swarm, new OrdenateSpacialPosition());

                final ParticleSwarmOptimization<String> particleSwarmOptimization = new ParticleSwarmOptimization<String>(
                        swarm, psoConfiguration.getTypeMoviment()[0],
                        numIteracoes, psoConfiguration
                                .getDepth(),
                        psoConfiguration.getRepositorysize(),
                        (ArrayList<String>) verify
                                .getClasses(),
                        verify.getObjectives());

                final long iniTime = System.currentTimeMillis();

                particleSwarmOptimization.evaluate();

                resultPSO.add(particleSwarmOptimization);

                final float time =
                        (float) ((System.currentTimeMillis() - iniTime) / 1000);

                // ordena a lista de part�culas
                Collections.sort(particleSwarmOptimization.getSwarm(),
                                 new OrdenatePareto<Particle<String>>(
                        new ParetoMinMinDominace()));

                Collections.sort(particleSwarmOptimization.getSwarm(),
                                 new OrdenateSpacialPosition());

                outFile.println("Sistema " + systemID
                        + " ParticleSwarmOptimization  - Resultado final ");

                if (psoConfiguration.getRepositorysize() > 1) {
                    outFile.println("Fronteira de Pareto Encontrada");
                }

                final List<Particle<String>> paretoFront =
                        particleSwarmOptimization.getParetoBorder();

                Collections.sort(paretoFront,
                                 new OrdenatePareto<Particle<String>>(
                        new ParetoMinMinDominace()));

                Collections.sort(paretoFront,
                                 new OrdenateSpacialPosition());

                for (Particle<String> particle : paretoFront) {
                    outFile.println("| " + particle.toString() + " |");
                }

                outFile
                        .println("Processamento encerrado. Tempo total: "
                        + time + " segundos.");

                outFile.flush();

                outFile.close();

                Logger.getLogger(this.getClass()).info("End");
            }
        } catch (IOException e) {
            Logger.getLogger(this.getClass()).error(
                                                    "Erro ao executar sistema "
                    + systemID, e);
        } catch (InterruptedException e) {
            Logger.getLogger(this.getClass()).error(
                                                    "Erro ao executar sistema "
                    + systemID, e);
        } catch (Exception e) {
            Logger.getLogger(this.getClass()).error(
                                                    "Erro ao executar sistema "
                    + systemID, e);
        }
        // notifica main thread
        stopSystem.countDown();
    }

}
